using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Net;
using System.Net.Browser;
using System.Xml.Linq;
using Caliburn.Micro;
using MediaOwl.LastFm.Model;

namespace MediaOwl.LastFm.Data
{
    /// <summary>
    /// The repository of the current Last.fm-Data. Gets renewed on every search.
    /// </summary>
    [Export(typeof(ILastFmDataRepository))]
    public class LastFmDataRepository : ILastFmDataRepository
    {
        #region Constructor

        public LastFmDataRepository()
        {
            WebRequest.RegisterPrefix(LastFmDataAccess.BaseUri, WebRequestCreator.ClientHttp);
            SearchDictionary = new Dictionary<Type, Search>();
        }

        #endregion

        #region Properties & Backingfields

        public IDictionary<Type, Search> SearchDictionary { get; set; }

        public Search CurrentArtistSearch
        {
            get { return SearchDictionary[typeof (Artist)]; }
        }

        public Search CurrentAlbumSearch
        {
            get { return SearchDictionary[typeof(Album)]; }
        }

        public Search CurrentTrackSearch
        {
            get { return SearchDictionary[typeof(Track)]; }
        }

        #endregion

        #region Methods

        public Search SetOrCreateSearch<T>(XElement xmlData)
            where T : EntityBase
        {
            var openSearchXml =
                xmlData.Descendants("results").Descendants().Where(
                    x =>
                    x.Name.Namespace == LastFmDataAccess.NameSpaceOpenSearch);

            var search = new Search(typeof(T), openSearchXml);
            SearchDictionary[typeof (T)] = search;
            return search;
        }

        #region Artist-Results

        public IResult GetArtists(string searchString, Action<IEnumerable<Artist>, Search> onSuccess, Action<Exception> onFail = null, int page = 0)
        {
            var p1 = new Parameter
            {
                Name = LastFmDataAccess.ParamMethod,
                Value = LastFmDataAccess.MethodArtistSearch
            };
            var p2 = new Parameter
            {
                Name = LastFmDataAccess.ParamArtist,
                Value = searchString
            };
            var p3 = new Parameter
            {
                Name = LastFmDataAccess.ParamPage,
                Value = page.ToString()
            };

            return new LoadDataResult<Artist>(Helper.BuildSearchString(new[] { p1, p2, p3 }), this, onSuccess, onFail);
        }
        
        public IResult GetArtists(Action<IEnumerable<Artist>, Search> onSuccess, Action<Exception> onFail = null, int page = 0)
        {
            return GetArtists(CurrentArtistSearch.SearchTerms, onSuccess, onFail, page);
        }

        public IResult GetArtist(string artistId, bool isMusicBrainzId, Action<Artist> onSuccess, Action<Exception> onFail = null)
        {
            var p1 = new Parameter
            {
                Name = LastFmDataAccess.ParamMethod,
                Value = LastFmDataAccess.MethodArtistGetInfo
            };
            Parameter p2;
            if (isMusicBrainzId)
            {
                p2 = new Parameter
                {
                    Name = LastFmDataAccess.ParamMBID,
                    Value = artistId
                };
            }
            else
            {
                p2 = new Parameter
                {
                    Name = LastFmDataAccess.ParamArtist,
                    Value = artistId
                };
            }
            var p3 = new Parameter
            {
                Name = LastFmDataAccess.ParamAutocorrect,
                Value = true.ToString()
            };

            return new LoadEntityResult<Artist>(Helper.BuildSearchString(new[] { p1, p2, p3 }), onSuccess, onFail);
        }

        public IResult GetArtist(ArtistBase artistBase, Action<Artist> onSuccess, Action<Exception> onFail = null)
        {
            return string.IsNullOrEmpty(artistBase.MusicBrainzId)
                ? GetArtist(artistBase.Name, false, onSuccess, onFail)
                : GetArtist(artistBase.MusicBrainzId, true, onSuccess, onFail);
        }

        public IResult GetBiographyOfArtist(Artist artist, Action<Biography> onSuccess, Action<Exception> onFail = null)
        {
            var p1 = new Parameter
            {
                Name = LastFmDataAccess.ParamMethod,
                Value = LastFmDataAccess.MethodArtistGetInfo
            };
            Parameter p2;
            if (string.IsNullOrEmpty(artist.MusicBrainzId))
            {
                p2 = new Parameter
                {
                    Name = LastFmDataAccess.ParamArtist,
                    Value = artist.Name
                };
            }
            else
            {
                p2 = new Parameter
                {
                    Name = LastFmDataAccess.ParamMBID,
                    Value = artist.MusicBrainzId
                };
            }
            var p3 = new Parameter
            {
                Name = LastFmDataAccess.ParamAutocorrect,
                Value = true.ToString()
            };

            return new LoadEntityResult<Biography>(Helper.BuildSearchString(new[] { p1, p2, p3 }), onSuccess, onFail);
        }

        public IResult GetTopAlbumsOfArtist(Artist artist, Action<IEnumerable<Album>> onSuccess, Action<Exception> onFail = null)
        {
            var p1 = new Parameter
            {
                Name = LastFmDataAccess.ParamMethod,
                Value = LastFmDataAccess.MethodArtistGetTopAlbums
            };
            Parameter p2;
            if (string.IsNullOrEmpty(artist.MusicBrainzId))
            {
                p2 = new Parameter
                {
                    Name = LastFmDataAccess.ParamArtist,
                    Value = artist.Name
                };
            }
            else
            {
                p2 = new Parameter
                {
                    Name = LastFmDataAccess.ParamMBID,
                    Value = artist.MusicBrainzId
                };
            }
            var p3 = new Parameter
            {
                Name = LastFmDataAccess.ParamAutocorrect,
                Value = true.ToString()
            };

            return new LoadDataResult<Album>(Helper.BuildSearchString(new[] { p1, p2, p3 }), this, onSuccess, onFail);
        }

        public IResult GetTopTracksOfArtist(Artist artist, Action<IEnumerable<Track>> onSuccess, Action<Exception> onFail = null)
        {
            var p1 = new Parameter
            {
                Name = LastFmDataAccess.ParamMethod,
                Value = LastFmDataAccess.MethodArtistGetTopTracks
            };
            Parameter p2;
            if (string.IsNullOrEmpty(artist.MusicBrainzId))
            {
                p2 = new Parameter
                {
                    Name = LastFmDataAccess.ParamArtist,
                    Value = artist.Name
                };
            }
            else
            {
                p2 = new Parameter
                {
                    Name = LastFmDataAccess.ParamMBID,
                    Value = artist.MusicBrainzId
                };
            }
            var p3 = new Parameter
            {
                Name = LastFmDataAccess.ParamAutocorrect,
                Value = true.ToString()
            };

            return new LoadDataResult<Track>(Helper.BuildSearchString(new[] { p1, p2, p3 }), this, onSuccess, onFail);
        }

        public IResult GetTopTagsOfArtist(Artist artist, Action<IEnumerable<Tag>> onSuccess, Action<Exception> onFail = null)
        {
            var p1 = new Parameter
            {
                Name = LastFmDataAccess.ParamMethod,
                Value = LastFmDataAccess.MethodArtistGetTopTags
            };
            Parameter p2;
            if (string.IsNullOrEmpty(artist.MusicBrainzId))
            {
                p2 = new Parameter
                {
                    Name = LastFmDataAccess.ParamArtist,
                    Value = artist.Name
                };
            }
            else
            {
                p2 = new Parameter
                {
                    Name = LastFmDataAccess.ParamMBID,
                    Value = artist.MusicBrainzId
                };
            }
            var p3 = new Parameter
            {
                Name = LastFmDataAccess.ParamAutocorrect,
                Value = true.ToString()
            };

            return new LoadDataResult<Tag>(Helper.BuildSearchString(new[] {p1, p2, p3}), this, onSuccess, onFail);
        }

        public IResult GetNextEventsOfArtist(Artist artist, Action<IEnumerable<Event>> onSuccess, Action<Exception> onFail = null)
        {
            var p1 = new Parameter
            {
                Name = LastFmDataAccess.ParamMethod,
                Value = LastFmDataAccess.MethodArtistGetEvents
            };
            Parameter p2;
            if (string.IsNullOrEmpty(artist.MusicBrainzId))
            {
                p2 = new Parameter
                {
                    Name = LastFmDataAccess.ParamArtist,
                    Value = artist.Name
                };
            }
            else
            {
                p2 = new Parameter
                {
                    Name = LastFmDataAccess.ParamMBID,
                    Value = artist.MusicBrainzId
                };
            }
            var p3 = new Parameter
            {
                Name = LastFmDataAccess.ParamAutocorrect,
                Value = true.ToString()
            };

            return new LoadDataResult<Event>(Helper.BuildSearchString(new[] { p1, p2, p3 }), this, onSuccess, onFail);
        }

        public IResult GetSimilarArtists(Artist artist, Action<IEnumerable<ArtistBase>> onSuccess, Action<Exception> onFail = null)
        {
            const int limit = 16;
            var p1 = new Parameter
            {
                Name = LastFmDataAccess.ParamMethod,
                Value = LastFmDataAccess.MethodArtistGetSimilar
            };
            Parameter p2;
            if (string.IsNullOrEmpty(artist.MusicBrainzId))
            {
                p2 = new Parameter
                {
                    Name = LastFmDataAccess.ParamArtist,
                    Value = artist.Name
                };
            }
            else
            {
                p2 = new Parameter
                {
                    Name = LastFmDataAccess.ParamMBID,
                    Value = artist.MusicBrainzId
                };
            }
            var p3 = new Parameter
            {
                Name = LastFmDataAccess.ParamAutocorrect,
                Value = true.ToString()
            };
            var p4 = new Parameter
            {
                Name = LastFmDataAccess.ParamLimit,
                Value = limit.ToString()
            };

            return new LoadDataResult<ArtistBase>(Helper.BuildSearchString(new[] { p1, p2, p3, p4 }), this, onSuccess, onFail);
        }

        #endregion

        #region Album-Results

        public IResult GetAlbums(string searchString, Action<IEnumerable<Album>, Search> onSuccess, Action<Exception> onFail = null, int page = 0)
        {
            var p1 = new Parameter
            {
                Name = LastFmDataAccess.ParamMethod,
                Value = LastFmDataAccess.MethodAlbumSearch
            };
            var p2 = new Parameter
            {
                Name = LastFmDataAccess.ParamAlbum,
                Value = searchString
            };
            var p3 = new Parameter
            {
                Name = LastFmDataAccess.ParamPage,
                Value = page.ToString()
            };
            return new LoadDataResult<Album>(Helper.BuildSearchString(new[] {p1, p2, p3}), this, onSuccess, onFail);
        }

        public IResult GetAlbums(Action<IEnumerable<Album>, Search> onSuccess, Action<Exception> onFail = null, int page = 0)
        {
            return GetAlbums(CurrentAlbumSearch.SearchTerms, onSuccess, onFail, page);
        }

        public IResult GetAlbum(string albumName, string artistName, Action<Album> onSuccess, Action<Exception> onFail = null, string musicBrainzId = null)
        {
            var p1 = new Parameter
            {
                Name = LastFmDataAccess.ParamMethod,
                Value = LastFmDataAccess.MethodAlbumGetInfo
            };

            var p2 = new Parameter
            {
                Name = LastFmDataAccess.ParamAutocorrect,
                Value = true.ToString()
            };
            Parameter p3;
            string searchString;
            if (string.IsNullOrEmpty(musicBrainzId))
            {
                p3 = new Parameter
                {
                    Name = LastFmDataAccess.ParamAlbum,
                    Value = albumName
                };
                var p4 = new Parameter
                {
                    Name = LastFmDataAccess.ParamArtist,
                    Value = artistName
                };
                searchString = Helper.BuildSearchString(new[] { p1, p2, p3, p4 });
            }
            else
            {
                p3 = new Parameter
                {
                    Name = LastFmDataAccess.ParamMBID,
                    Value = musicBrainzId
                };
                searchString = Helper.BuildSearchString(new[] { p1, p2, p3 });
            }

            return new LoadEntityResult<Album>(searchString, onSuccess, onFail);
        }

        public IResult GetAlbum(Album album, Action<Album> onSuccess, Action<Exception> onFail = null)
        {
            return GetAlbum(album.Name, album.ArtistName, onSuccess, onFail, album.MusicBrainzId);
        }

        #endregion

        #region Track-Results

        public IResult GetTracks(string searchString, Action<IEnumerable<Track>, Search> onSuccess, Action<Exception> onFail = null, string artistName = null, int page = 0)
        {
            var p1 = new Parameter
            {
                Name = LastFmDataAccess.ParamMethod,
                Value = LastFmDataAccess.MethodTrackSearch
            };
            var p2 = new Parameter
            {
                Name = LastFmDataAccess.ParamTrack,
                Value = searchString
            };
            var p3 = new Parameter
            {
                Name = LastFmDataAccess.ParamPage,
                Value = page.ToString()
            };
            string builtSearchString;
            if (string.IsNullOrEmpty(artistName))
            {
                builtSearchString = Helper.BuildSearchString(new[] { p1, p2, p3 });
            }
            else
            {
                var p4 = new Parameter
                {
                    Name = LastFmDataAccess.ParamArtist,
                    Value = artistName
                };
                builtSearchString = Helper.BuildSearchString(new[] { p1, p2, p3, p4 });
            }
            return new LoadDataResult<Track>(builtSearchString, this, onSuccess, onFail);
        }

        public IResult GetTracks(Action<IEnumerable<Track>, Search> onSuccess, Action<Exception> onFail = null, int page = 0)
        {
            return GetTracks(CurrentTrackSearch.SearchTerms, onSuccess, onFail, null, page);
        }

        public IResult GetTrack(string trackName, string artistName, Action<Track> onSuccess, Action<Exception> onFail = null, string musicBrainzId = null)
        {
            var p1 = new Parameter
            {
                Name = LastFmDataAccess.ParamMethod,
                Value = LastFmDataAccess.MethodTrackGetInfo
            };

            var p2 = new Parameter
            {
                Name = LastFmDataAccess.ParamAutocorrect,
                Value = true.ToString()
            };
            Parameter p3;
            string searchString;
            if (string.IsNullOrEmpty(musicBrainzId))
            {
                p3 = new Parameter
                {
                    Name = LastFmDataAccess.ParamTrack,
                    Value = trackName
                };
                var p4 = new Parameter
                {
                    Name = LastFmDataAccess.ParamArtist,
                    Value = artistName
                };
                searchString = Helper.BuildSearchString(new[] { p1, p2, p3, p4 });
            }
            else
            {
                p3 = new Parameter
                {
                    Name = LastFmDataAccess.ParamMBID,
                    Value = musicBrainzId
                };
                searchString = Helper.BuildSearchString(new[] { p1, p2, p3 });
            }

            return new LoadEntityResult<Track>(searchString, onSuccess, onFail);
        }

        public IResult GetTrack(Track track,  Action<Track> onSuccess, Action<Exception> onFail = null)
        {
            return GetTrack(track.Name, track.ArtistName, onSuccess, onFail, track.MusicBrainzId);
        }

        public IResult GetSimilarTracks(Track track, Action<IEnumerable<Track>> onSuccess, Action<Exception> onFail = null)
        {
            const int limit = 16;
            string searchString;
            var p1 = new Parameter
            {
                Name = LastFmDataAccess.ParamMethod,
                Value = LastFmDataAccess.MethodTrackGetSimilar
            };

            var p2 = new Parameter
            {
                Name = LastFmDataAccess.ParamAutocorrect,
                Value = true.ToString()
            };
            var p3 = new Parameter
            {
                Name = LastFmDataAccess.ParamLimit,
                Value = limit.ToString()
            };

            if (string.IsNullOrEmpty(track.MusicBrainzId))
            {
                var p4 = new Parameter
                {
                    Name = LastFmDataAccess.ParamArtist,
                    Value = track.ArtistName
                };

                var p5 = new Parameter
                {
                    Name = LastFmDataAccess.ParamTrack,
                    Value = track.Name
                };
                searchString = Helper.BuildSearchString(new[] { p1, p2, p3, p4, p5 });
            }
            else
            {
                var p4 = new Parameter
                {
                    Name = LastFmDataAccess.ParamMBID,
                    Value = track.MusicBrainzId
                };
                searchString = Helper.BuildSearchString(new[] { p1, p2, p3, p4 });
            }

            return new LoadDataResult<Track>(searchString, this, onSuccess, onFail);
        }

        #endregion

        #region Tag-Results

        public IResult GetTopTags(Action<IEnumerable<Tag>> onSuccess, Action<Exception> onFail = null)
        {
            var p1 = new Parameter
            {
                Name = LastFmDataAccess.ParamMethod,
                Value = LastFmDataAccess.MethodTagGetTopTags
            };
            var p2 = new Parameter
            {
                Name = LastFmDataAccess.ParamLimit,
                Value = 100.ToString()
            };
            return new LoadDataResult<Tag>(Helper.BuildSearchString(new[] { p1, p2 }), this, onSuccess, onFail);
        }

        public IResult GetTopAlbumsOfTag(Tag tag, Action<IEnumerable<Album>> onSuccess, Action<Exception> onFail = null)
        {
            var p1 = new Parameter
            {
                Name = LastFmDataAccess.ParamMethod,
                Value = LastFmDataAccess.MethodTagGetTopAlbums
            };
            var p2 = new Parameter
            {
                Name = LastFmDataAccess.ParamTag,
                Value = tag.Name
            };

            return new LoadDataResult<Album>(Helper.BuildSearchString(new[] { p1, p2 }), this, onSuccess, onFail);
        }

        public IResult GetTopArtistsOfTag(Tag tag, Action<IEnumerable<Artist>> onSuccess, Action<Exception> onFail = null)
        {
            var p1 = new Parameter
            {
                Name = LastFmDataAccess.ParamMethod,
                Value = LastFmDataAccess.MethodTagGetTopArtists
            };
            var p2 = new Parameter
            {
                Name = LastFmDataAccess.ParamTag,
                Value = tag.Name
            };

            return new LoadDataResult<Artist>(Helper.BuildSearchString(new[] { p1, p2 }), this, onSuccess, onFail);
        }

        public IResult GetTopTracksOfTag(Tag tag, Action<IEnumerable<Track>> onSuccess, Action<Exception> onFail = null)
        {
            var p1 = new Parameter
            {
                Name = LastFmDataAccess.ParamMethod,
                Value = LastFmDataAccess.MethodTagGetTopTracks
            };
            var p2 = new Parameter
            {
                Name = LastFmDataAccess.ParamTag,
                Value = tag.Name
            };

            return new LoadDataResult<Track>(Helper.BuildSearchString(new[] { p1, p2 }), this, onSuccess, onFail);
        }

        public IResult GetWeeklyArtistChartOfTag(Tag tag, Action<IEnumerable<Artist>> onSuccess, Action<Exception> onFail = null)
        {
            var p1 = new Parameter
            {
                Name = LastFmDataAccess.ParamMethod,
                Value = LastFmDataAccess.MethodTagGetWeeklyArtistChart
            };
            var p2 = new Parameter
            {
                Name = LastFmDataAccess.ParamTag,
                Value = tag.Name
            };

            return new LoadDataResult<Artist>(Helper.BuildSearchString(new[] { p1, p2 }), this, onSuccess, onFail);
        } 

        #endregion

        #endregion
    }
}